#include <stdio.h>
#include <iostream>
#include <stdlib.h>

//#include <GL/gl.h> <-- included in glut.h
//#include <GL/glu.h> <-- included in glut.h

#include <GL/glew.h>
#include <GL/glut.h>

#include <Cg/cg.h>
#include <Cg/cgGL.h>


using namespace std;


int width  = 512;
int height = 512;

// the texture to hold data on the GPU
GLuint tex;
GLuint texture;

// arrays on the CPU to hold the data and result of GPU calucualtion
GLfloat* dataArr;
GLfloat* resultArr;

// some Cg variables
CGcontext cgContext;
CGprofile fragmentProfile;
CGprogram fragmentProgram;
CGparameter yParam, xParam, alphaParam;
const char *programSource = "kernels/ident.frag";

static GLint texSize = 4;
static GLenum textarget = GL_TEXTURE_2D;
static GLenum texinternalformat = GL_RGBA_FLOAT32_ATI;

// CPU arrays for data and to hold the result once returned from graphics card
float* data = (float*)malloc(4*texSize*texSize*sizeof(float));
float* result = (float*)malloc(4*texSize*texSize*sizeof(float));

int N = 4*texSize*texSize;
float* dataY = (float*)malloc(N*sizeof(float));
float* dataX = (float*)malloc(N*sizeof(float));
float alpha = 2.0;



// handle to frame buffer object - fbo (offscreen buffer) 
GLuint fb; 



// prototypes
void initFBO(void); // initialise the offscreen buffer

void createCPUdata();
void set1to1Mapping();
void initGPUTextures();
void initCgProg();
void printResults();
void render();
void readBacktoCPU();
void setTextureAsRenderTarget();





int main(int argc, char **argv) 
{
   createCPUdata();
   
   // set up glut to get valid GL context and 
   // get extension entry points
   glutInit (&argc, argv);
   glutCreateWindow("TEST1");
   glewInit();

   set1to1Mapping();
   
   initGPUTextures();
   initCgProg();

   render();
   readBacktoCPU();
   
   printResults();

   // clean up
   free(data);
   free(result);
   glDeleteFramebuffersEXT (1,&fb);
   glDeleteTextures (1,&tex);
   cgDestroyContext(cgContext);

   return 0;
}








void createCPUdata()
{
   // declare texture size, the actual data will be a vector 
   // of size texSize*texSize*4
   // create test data and fill arbitrarily on CPU
   for (int i=0; i<texSize*texSize*4; i++)
      data[i] = i+1.0;
}


void set1to1Mapping()
{
   // viewport transform for 1:1 pixel=texel=data mapping
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0,texSize,0.0,texSize);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glViewport(0,0,texSize,texSize);
}


void initFBO(void)
{
   // create FB0 (off-screen frame buffer)
   // note that binding FBO to number 0 will restore
   // the window-system specific framebuffer at any time.
   glGenFramebuffersEXT(1, &fb);
   // bind offscreen buffer
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
   return;
}


void initGPUTextures()
{
   // create texture
   glGenTextures (1, &tex);
   glBindTexture(textarget,tex);
   
   // set texture parameters 
   // this assumes the textures are already generated and bound
   // with glGenTextures()  and ...
   // glBindTexture()
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);

   // define texture with floating point format 
   glTexImage2D(textarget,0,texinternalformat,texSize,
		texSize,0,GL_RGBA,GL_FLOAT,0);
   

   //========================================
   
   glGenTextures(1, &texture);
   // bind the texture name to a texture target
   glBindTexture(textarget,texture);
   // turn off filtering and set proper wrap mode
   // (obligatory for float textures at the moment)
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
   
   

   // set texenv to replace instrea of the default modulate
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
   // allocate graphics memory for the texture
   glTexImage2D(textarget, 0, texinternalformat, texSize, texSize,
		0, GL_RGBA, GL_FLOAT, 0);
   
   return;
}


void setTextureAsRenderTarget()
{
   // attach texture
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
			     GL_COLOR_ATTACHMENT0_EXT,
			     textarget,tex,0);
}


void initCgProg()
{
   // set up Cg
   cgContext = cgCreateContext();
   fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
   cgGLSetOptimalOptions(fragmentProfile);
   // create fragment program
   fragmentProgram = cgCreateProgramFromFile(
		     cgContext, CG_SOURCE, programSource,
		     fragmentProfile, "fp", NULL);
   
   // load program
   cgGLLoadProgram(fragmentProgram);
   
   // get parameter handles by name
   xParam =  cgGetNamedParameter(fragmentProgram, "dataArr");
   //alphaParam = cgGetNamedParameter(fragmentProgram, "alpha");
   //textureHandle = cgGetNamedParameter(cgProgram, "data");
}



void printResults() {
   // print out results
   printf("Data before roundtrip:\n");
   for (int i=0; i<texSize*texSize*4; i++)
      printf("%f\n",data[i]);
   printf("Data after roundtrip:\n");
   for (int i=0; i<texSize*texSize*4; i++)
      printf("%f\n",result[i]);
}


void render() {
   initFBO();
   setTextureAsRenderTarget();
   
   
   // enable fragment profile
   cgGLEnableProfile(fragmentProfile);
   // bind fragment program (in this case set up to be ident.frag
   cgGLBindProgram(fragmentProgram);
   
   // setting Input arrays/textures
   cgGLSetTextureParameter(yParam, texture);
   cgGLEnableTextureParameter(yParam);
   
   // enable texture x (read only)
   cgGLSetTextureParameter(xParam, tex);
   cgGLEnableTextureParameter(xParam);
   
   // enable scalar alpha
   cgGLSetParameter1f(alphaParam, alpha);
   
   // transfer data to texture
   glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); 
   glRasterPos2i(0,0); // reset the raster postion to (top left?)
   glDrawPixels(texSize,texSize,GL_RGBA,GL_FLOAT,data);

   // make quad filled to hit every pixel/texel
   glPolygonMode(GL_FRONT,GL_FILL);
   // render quad
   float v_tex = texSize;
   float h_tex = texSize;
   glBegin(GL_QUADS);
     glTexCoord2f(0.0,0.0);   //1 
     glVertex2f(-1.0,-1.0);   //1
     glTexCoord2f(0.0,v_tex); //2
     glVertex2f(-1.0,1.0);    //2
     glTexCoord2f(h_tex,v_tex); //3
     glVertex2f(1.0,1.0);       //3
     glTexCoord2f(h_tex,0.0); //4
     glVertex2f(1.0,-1.0);    //4
   glEnd();


   cgGLDisableProfile(fragmentProfile);

   
}


void readBacktoCPU()
{
   // and read back                             
   glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);   
   glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);        
}
